# -*- coding: utf-8 -*-
# @Time: 2023/4/14 15:08
# @File: views.py
# @Desc: 前端视图代码

from flask import Blueprint, request, render_template, current_app, \
    make_response, session, redirect, g, jsonify, url_for
import string
import random
from exts import cache, db
from utils import restful
from utils.captcha import Captcha
from hashlib import md5
import time
from io import BytesIO
from .forms import RegisterForm, LoginForm, UploadAvatarForm, ProfileForm, \
    UploadImageFrom, PublishPostForm, PublishCommentForm
from models.auth import User, Permission
from models.common import Board, Post, Comment, Banner
from .decorators import loginrequired
from flask_avatars import Identicon
import os
from flask_paginate import Pagination, get_page_parameter
from sqlalchemy.sql import func
from flask_jwt_extended import create_access_token

bp_front = Blueprint("front", __name__, url_prefix="/")


# 钩子函数  在调用视图之前执行
@bp_front.before_request
def before_request():
    if 'user_id' in session:
        user_id = session.get('user_id')
        user = User.query.get(user_id)
        setattr(g, "user", user)


# 上下文处理器
@bp_front.context_processor
def front_context_processor():
    if hasattr(g, "user"):
        return {"user": g.user}
    else:
        return {}


# 首页
@bp_front.route("/", methods=["GET", "POST"])
def index():
    s = request.args.get('st', type=int, default=1)
    b = request.args.get('bd', type=int, default=None)
    boards = Board.query.order_by(Board.priority.desc()).all()
    post_query = Post.query
    if s == 1:  # 按照帖子最新发布时间排序
        post_query = post_query.order_by(Post.create_date.desc())
    elif s == 2:  # 按照帖子评论数进行排序
        # 外连接comment表根据post.id进行分组，根据comment表中评论数量进行排序，
        # 如果数量相同按照post表中发布时间进行倒序排序
        post_query = db.session.query(Post).outerjoin(Comment). \
            group_by(Post.id). \
            order_by(func.count(Comment.id).desc(), Post.create_date.desc())
    page = request.args.get(get_page_parameter(), type=int, default=1)
    start = (page - 1) * current_app.config["PER_PAGE_COUNT"]
    end = start + current_app.config["PER_PAGE_COUNT"]
    if b:  # 根据板块过滤
        post_query = post_query.filter(Post.board_id == b)
    total = post_query.count()  # 获取帖子总数
    posts = post_query.slice(start, end)  # 设置分页
    banners = Banner.query.order_by(Banner.priority.desc()).all()
    pagination = Pagination(bs_version=3, page=page, total=total, prev_label="上一页", next_label="下一页")
    context = {
        "boards": boards,
        "posts": posts,
        "pagination": pagination,
        "st": s,
        "bd": b,
        "banners": banners
    }
    return render_template("front/index.html", **context)


# 注册
@bp_front.route("/register/", methods=["GET", "POST"])
def register():
    if request.method == "GET":
        return render_template("front/register.html")
    else:
        form = RegisterForm(request.form)
        if form.validate():
            email = form.email.data
            username = form.username.data
            password = form.password.data
            identicon = Identicon()
            filenames = identicon.generate(text=md5(email.encode('utf8')).hexdigest())
            avatar = filenames[2]
            user = User(email=email, username=username, password=password, avatar=avatar)
            db.session.add(user)
            db.session.commit()
            session['user_id'] = user.id
            return restful.success()
        else:
            message = form.message[0]
            return restful.params_error(message=message)


# 管理后台
@bp_front.get("/cms/")
def getcms():
    return render_template("cms/index.html")


# 登录
@bp_front.route("/login/", methods=["GET", "POST"])
def login():
    if request.method == "GET":
        return render_template("front/login.html")
    else:
        form = LoginForm(request.form)
        if form.validate():
            email = form.email.data
            password = form.password.data
            remember = form.remember.data
            user = User.query.filter_by(email=email).first()
            if not user:  # 用户不存在
                return restful.params_error(message="邮箱不存在!")
            if not user.check_password(password):  # 密码不匹配
                return restful.params_error(message="密码错误!!")
            if remember == 1:  # 记住我
                session.permanent = True
            session['user_id'] = user.id
            token = ""
            permissions = []
            if user.is_staff:
                # 如果用户具有后台权限，设置jwt鉴权token
                token = create_access_token(identity=user.id)
                for attr in dir(Permission):
                    if not attr.startswith("_"):  # 如果不是以"__"开头的，就是我们自定义的权限
                        permission = getattr(Permission, attr)
                        if user.check_permission(permission):  # 判断用户是否有权限
                            permissions.append(attr.lower())
            user_dict = user.to_dict()
            user_dict["permissions"] = permissions
            return restful.success(data={"token": token, "user": user_dict})  # 返回token和user序列化内容

        else:
            return restful.params_error(message=form.message[0])


# 退出登录
@bp_front.route("/layout/")
def layout():
    session.clear()
    return redirect("/")


# 个人信息设置
@bp_front.route("/setting/", methods=["POST", "GET"])
@loginrequired
def setting():
    email_hash = md5(g.user.id.encode("utf-8")).hexdigest()
    return render_template("front/setting.html", email_hash=email_hash)


# 上传用户头像
@bp_front.route("/avatar/upload/", methods=["POST"])
@loginrequired
def avatar_upload():
    form = UploadAvatarForm(request.files)
    if form.validate():
        image = form.image.data
        filename = image.filename
        # image_path = uploadfiles.upload_files(filename, g.user.id, current_app.config["AVATARS_SAVE_PATH"])
        _, ext = os.path.splitext(filename)  # 拆分文件名
        filename = md5((g.user.id + str(time.time())).encode("utf8")).hexdigest() + ext  # 重命名文件名
        image_path = os.path.join(current_app.config["AVATARS_SAVE_PATH"], filename)  # 获取图片保存路径
        image.save(image_path)
        g.user.avatar = filename
        db.session.commit()
        return restful.success(data={"avatar": filename})
    else:
        return restful.params_error(message=form.message[0])


# 保存用户信息
@bp_front.post("/profile/edit/")
@loginrequired
def profile_edit():
    form = ProfileForm(request.form)
    if form.validate():
        signature = form.signature.data
        g.user.signature = signature
        db.session.commit()
        return restful.success()
    else:
        return restful.params_error(message=form.message[0])


"""
# 发送邮件测试(未使用异步和缓存)
@bp_front.get("/email/captcha")
def email_captcha():
    email = request.args.get("email")
    if not email:
        # return jsonify({"code": 400, "message": "请先输入邮箱"})
        return restful.params_error(message="请先输入邮箱")
    # 生成4位数的邮箱验证码
    source = list(string.digits)
    captcha = "".join(random.sample(source, 4))
    subject = "BBS论坛注册验证码"
    recipients = [email]
    body = "【BBS论坛】注册邮箱验证码是:%s" % captcha
    message = Message(subject=subject, recipients=recipients, body=body)
    try:
        mail.send(message)
    except Exception as e:
        print(e)
        # return jsonify({"code": 500, "message": "邮件发送失败"})
        return restful.params_error(message="邮件发送失败!")
    # return jsonify({"code": 200, "message": "邮件发送成功"})
    return restful.success(message="邮件发送成功!")
"""


# 使用celery发送邮件测试(对原发送邮箱验证进行优化)
@bp_front.get("/email/captcha")
def celery_email_captcha():
    email = request.args.get("email")
    if not email:
        # return jsonify({"code": 400, "message": "请先输入邮箱"})
        return restful.params_error(message="请先输入邮箱！")
    # 生成4位数的邮箱验证码
    source = list(string.digits)
    captcha = "".join(random.sample(source, 4))
    subject = "BBS论坛注册验证码"
    body = "【BBS论坛】注册邮箱验证码是:%s" % captcha
    current_app.celery.send_task("send_mail", (email, subject, body))  # 使用celery发送邮件验证码
    cache.set(email, captcha)  # 将邮箱和验证码保存到到缓存中
    # print(cache.get(email))  # 测试保存结果
    # return jsonify({"code": 200, "message": "邮件发送成功"})
    return restful.success(message="邮件发送成功!")


"""
备注:
# 在python中操作redis需要安装两个包:
# 1. pip install redis
# 2. pip install hiredis

# 使用celery包
# pip install celery
# 在windows中使用celery需要借助gevent  pip install gevent


# 在终端执行: celery -A app.mycelery worker --loglevel=info
# app.mycelery  这个mycelery是根据app.py文件中定义的 mycelery = make_celery(app)来的

# 缓存验证码Flask-Caching,使用redis
# 安装 pip install flask-caching
"""


# 图形验证码
@bp_front.route("/graph/captcha")
def graph_captcha():
    captcha, image = Captcha.gene_graph_captcha()
    key = md5((captcha + str(time.time())).encode("utf-8")).hexdigest()
    cache.set(key, captcha)  # 将图形验证码放入缓存中
    out = BytesIO()
    image.save(out, "png")  # 保存图片到内存二进制流中
    out.seek(0)  # 将out的文件指针设置到最开始位置
    resp = make_response(out.read())  # 读取文件并放置到response对象中(返回给客户端的数据)
    resp.content_type = "image/png"  # 指定文件返回类型
    resp.set_cookie("_graph_captcha_key", key, max_age=3600)  # 将key保存到cookie中,返回到客户端,有效时间1小时
    return resp


# wangeditor上传图片（配置富文本编辑器）
@bp_front.post("/post/image/upload/")
@loginrequired
def upload_post_image():
    form = UploadImageFrom(request.files)
    if form.validate():
        image = form.image.data
        filename = image.filename
        _, ext = os.path.splitext(filename)  # 拆分文件名
        filename = md5((g.user.id + str(time.time())).encode("utf8")).hexdigest() + ext  # 重命名文件名
        image_path = os.path.join(current_app.config["POST_IMAGE_SAVE_PATH"], filename)  # 获取图片保存路径
        image.save(image_path)

        return jsonify({"errno": 0, "data": [{
            "url": url_for("media.get_post_image", filename=filename),
            "alt": filename,
            "href": ""
        }]})
    else:
        return restful.params_error(message=form.messages[0])


# 发布帖子
@bp_front.route("/post/publish/", methods=["POST", "GET"])
@loginrequired
def publish_post():
    if request.method == "GET":
        boards = Board.query.order_by(Board.priority.desc()).all()
        context = {
            "boards": boards
        }
        return render_template("front/post_publish.html", **context)
    else:
        form = PublishPostForm(request.form)
        if form.validate():
            title = form.title.data
            board_id = form.board_id.data
            content = form.content.data
            try:
                board = Board.query.get(board_id)
            except Exception as e:
                return restful.params_error("板块不存在，请选择已存在板块！")
            post = Post(title=title, board_id=board_id, content=content)
            post.author_id = g.user.id
            db.session.add(post)
            db.session.commit()
            return restful.success(data={"id": post.id})
        else:
            return restful.params_error(message=form.messages[0])


# 帖子详情页面
@bp_front.get("/post/detail/<int:pid>/")
@loginrequired
def post_detail(pid):
    post = Post.query.get(pid)
    content_count = Comment.query.filter_by(post_id=pid).count()
    context = {
        "content_count": content_count,
        "post": post
    }
    return render_template("front/post_detail.html", **context)


# 发表帖子评论
@bp_front.post("/comment/publish/")
@loginrequired
def publish_comment():
    form = PublishCommentForm(request.form)
    if form.validate():
        content = form.content.data
        post_id = form.post_id.data
        try:
            post = Post.query.get(post_id)
        except Exception as e:
            return restful.params_error(message="帖子不存在")
        comment = Comment(content=content, post_id=post_id)
        comment.user_id = g.user.id
        db.session.add(comment)
        db.session.commit()
        return restful.success()
    else:
        return restful.params_error(message=form.messages[0])
